package com.mallcloud.mall.auth.handler;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;
import com.mallcloud.mall.common.core.constant.CacheConstants;
import com.mallcloud.mall.common.core.constant.CommonConstants;
import com.mallcloud.mall.common.core.constant.SecurityConstants;
import com.mallcloud.mall.common.core.constant.enums.CaptchaFlagTypeEnum;
import com.mallcloud.mall.common.core.constant.enums.EncFlagTypeEnum;
import com.mallcloud.mall.common.core.exception.ValidateCodeException;
import com.mallcloud.mall.common.core.util.R;
import com.mallcloud.mall.common.core.util.SpringContextHolder;
import com.mallcloud.mall.common.core.util.WebUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * 登录前置处理
 *
 * 1. 验证码校验 2. 密码解密
 */
@Slf4j
@Aspect
@Service
public class MallLoginPreHandler {

	private static final String PASSWORD = "password";

	private static final String KEY_ALGORITHM = "AES";

	@Pointcut("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
	private void postAccessToken() {
	}

	@Around("postAccessToken()")
	@SneakyThrows
	public Object aroundAdvice(ProceedingJoinPoint point) {
		WebUtils.getRequest().setAttribute(CommonConstants.REQUEST_START_TIME, System.currentTimeMillis());

		Object[] args = point.getArgs();
		Map<String, String> parameters = (Map<String, String>) args[1];
		// 刷新token，直接向下执行
		String grantType = parameters.get("grant_type");
		if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
			return point.proceed();
		}

		// 获取客户端配置
		JSONObject information = getClientConfig();
		if (information == null) {
			decode(parameters);
			return point.proceed();
		}

		// 校验验证码 1. 客户端开启验证码 2. 短信模式
		if (StrUtil.equals(CaptchaFlagTypeEnum.ON.getType(), information.getStr(CommonConstants.CAPTCHA_FLAG))
				|| StrUtil.contains(parameters.get(SecurityConstants.GRANT_MOBILE), "SMS")) {
			try {
				checkCode();
			}
			catch (ValidateCodeException validateCodeException) {
				return new ResponseEntity<R>(HttpStatus.PRECONDITION_REQUIRED);
			}
		}

		// 密码解密
		if (StrUtil.equals(EncFlagTypeEnum.YES.getType(), information.getStr(CommonConstants.ENC_FLAG))) {
			decode(parameters);
		}

		return point.proceed(args);
	}

	/**
	 * 密码解密
	 * @param params
	 */
	private void decode(Map<String, String> params) {
		// 构建前端对应解密AES 因子
		String key = SpringContextHolder.getBean(Environment.class).getProperty("gateway.encodeKey", "mallmallmallmall");
		AES aes = new AES(Mode.CFB, Padding.NoPadding, new SecretKeySpec(key.getBytes(), KEY_ALGORITHM), new IvParameterSpec(key.getBytes()));
		log.info("登录密码解密：{}", aes.decryptStr(params.get(PASSWORD)));
		params.put(PASSWORD, aes.decryptStr(params.get(PASSWORD)));
	}

	/**
	 * 校验验证码
	 */
	private void checkCode() throws ValidateCodeException {
		HttpServletRequest request = WebUtils.getRequest();
		String code = request.getParameter("code");

		if (StrUtil.isBlank(code)) {
			throw new ValidateCodeException("验证码不能为空");
		}

		String randomStr = request.getParameter("randomStr");

		// 若是滑块登录
		if (CommonConstants.IMAGE_CODE_TYPE.equalsIgnoreCase(randomStr)) {
			CaptchaService captchaService = SpringContextHolder.getBean(CaptchaService.class);
			CaptchaVO vo = new CaptchaVO();
			vo.setCaptchaVerification(code);
			vo.setCaptchaType(CommonConstants.IMAGE_CODE_TYPE);
			if (!captchaService.verification(vo).isSuccess()) {
				throw new ValidateCodeException("验证码不能为空");
			}
			return;
		}

		// https://gitee.com/log4j/pig/issues/IWA0D
		String mobile = request.getParameter("mobile");
		if (StrUtil.isNotBlank(mobile)) {
			randomStr = mobile;
		}

		String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
		redisTemplate.setKeySerializer(new StringRedisSerializer());

		if (!redisTemplate.hasKey(key)) {
			throw new ValidateCodeException("验证码不合法");
		}

		Object codeObj = redisTemplate.opsForValue().get(key);

		if (codeObj == null) {
			throw new ValidateCodeException("验证码不合法");
		}

		String saveCode = codeObj.toString();
		if (StrUtil.isBlank(saveCode)) {
			redisTemplate.delete(key);
			throw new ValidateCodeException("验证码不合法");
		}

		if (!StrUtil.equals(saveCode, code)) {
			redisTemplate.delete(key);
			throw new ValidateCodeException("验证码不合法");
		}

		redisTemplate.delete(key);
	}

	/**
	 * 获取终端扩展配置
	 * @return JSONObject
	 */
	private JSONObject getClientConfig() {
		String header = WebUtils.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
		String clientId = WebUtils.extractClientId(header).orElse(null);
		// 获取租户拼接区分租户的key
		String tenantId = WebUtils.getRequest().getHeader(CommonConstants.TENANT_ID);
		String key = String.format("%s:%s:%s", StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1 : tenantId,
				CacheConstants.CLIENT_FLAG, clientId);

		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		Object val = redisTemplate.opsForValue().get(key);
		// 当配置不存在时，不用校验
		if (val == null) {
			return null;
		}
		return JSONUtil.parseObj(val.toString());
	}

}
